package com.mab.mall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.mab.mall.search.config.MallElasticSearchConfiguration;
import com.mab.mall.search.service.SearchService;
import com.mab.mall.search.vo.SearchParam;
import com.mab.mall.search.vo.SearchResult;
import com.msb.common.constant.ElasticSearchConstant;
import com.msb.common.dto.es.SkuESModel;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 商品检索处理
     * 模糊匹配，关键字匹配
     * 过滤（类别，品牌，属性，价格区间，库存）
     * 聚合
     * 排序，分页，高亮
     * @param param
     * @return
     */
    @Override
    public SearchResult search(SearchParam param) {
        SearchRequest searchRequest = new SearchRequest();
        // 检索的索引库
        searchRequest.indices(ElasticSearchConstant.PRODUCT_UP);
        // 检索的条件
        SearchSourceBuilder searchBuilder = new SearchSourceBuilder();
        // 组合查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.关键字条件
        if (!StringUtils.isNullOrEmpty(param.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("subTitle",param.getKeyword()));
        }
        // 2.类别检索条件
        if (null != param.getCatalog3Id()){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
        }
        // 3.品牌的检索条件
        if (null != param.getBrandId() && param.getBrandId().size() > 0){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
        }
        // 4.是否有库存
        if (null != param.getHasStock()){
            boolQuery.filter(QueryBuilders.termQuery("hasStock",param.getHasStock()==1?true:false));
        }
        // 5.价格区间
        if (!StringUtils.isNullOrEmpty(param.getSkuPrice())){
            String[] priceMsg = param.getSkuPrice().split("_");
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
            switch (priceMsg.length){
                case 1:
                    // 说明价格区间是小于或者大于：_300/200_
                    if (param.getSkuPrice().endsWith("_")){
                        // 大于
                        rangeQueryBuilder.gte(priceMsg[0]);
                    }else if (param.getSkuPrice().startsWith("_")){
                        // 小于
                        rangeQueryBuilder.lte(priceMsg[0]);
                    }
                    break;
                case 2:
                    // 说明价格区间是大于并且小于：200_300
                    rangeQueryBuilder.gte(StringUtils.isNullOrEmpty(priceMsg[0])?0:priceMsg[0]).lte(priceMsg[1]);
                    break;
            }
            boolQuery.filter(rangeQueryBuilder);
        }
        // 6.属性条件的检索
        // 属性多选，字符串拼接方式：attrs=20_8英寸:10英寸&attrs=19_32GB:64GB
        if (null != param.getAttrs() && param.getAttrs().size() > 0){
            for (String attr : param.getAttrs()) {
                // 19_32GB:64GB
                // 首先利用"_"分割，前面是属性id，后面是属性
                String[] attrStrArray = attr.split("_");
                String attrId = attrStrArray[0];
                String[] attrValueArray = attrStrArray[1].split(":");
                // 字段分割出来，数据需要使用Nested检索。（这种类型匹配原则，否则结果会不理想）
                BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                boolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                boolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",attrValueArray));
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs",boolQueryBuilder, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }
        searchBuilder.query(boolQuery);
        // 7.分页，高亮，排序等条件
        if (null == param.getPageNum()){
            param.setPageNum(1);
        }
        // 分页处理，传过来是页码，
        // 从第几条开始，（页码-1）*每页展示的条数
        searchBuilder.from((param.getPageNum()-1)*ElasticSearchConstant.PRODUCT_PAGE_SIZE);
        searchBuilder.size(ElasticSearchConstant.PRODUCT_PAGE_SIZE);

        // 查询的关键字需要进行高亮显示
        if (!StringUtils.isNullOrEmpty(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("subTitle");
            // 高亮前缀设置
            highlightBuilder.preTags("<b style='color:red'>");
            // 高亮后缀设置
            highlightBuilder.postTags("</b>");
            searchBuilder.highlighter(highlightBuilder);
        }
        // 排序
        if (!StringUtils.isNullOrEmpty(param.getSort())){
            // 排序字段：sort=salaCount_asc 或者 sort=salaCount_desc
            String[] sortStr = param.getSort().split("_");
            searchBuilder.sort(sortStr[0]
                    ,sortStr[1].equalsIgnoreCase("asc")? SortOrder.ASC: SortOrder.DESC);
        }

        // 聚合检索条件
        // 品牌的聚合
        TermsAggregationBuilder brand_id_agg = AggregationBuilders.terms("brand_id_agg");
        brand_id_agg.field("brandId");
        brand_id_agg.size(10);
        brand_id_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(10));
        brand_id_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(10));
        searchBuilder.aggregation(brand_id_agg);

        // 类别的聚合
        TermsAggregationBuilder catalog_id_agg = AggregationBuilders.terms("catalog_id_agg");
        catalog_id_agg.field("catalogId");
        catalog_id_agg.size(10);
        catalog_id_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(10));
        searchBuilder.aggregation(catalog_id_agg);

        // 属性的聚合  nested类型
        NestedAggregationBuilder nested = AggregationBuilders.nested("attrs_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg");
        attr_id_agg.field("attrs.attrId").size(10);
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));
        nested.subAggregation(attr_id_agg);
        searchBuilder.aggregation(nested);

        searchRequest.source(searchBuilder);
        // 执行检索操作并对检索结果处理
        SearchResult result = searchResult(searchRequest,param);
        log.info("-----------------商品检索结果{}"+result);
        return result;
    }

    /**
     * 执行检索操作--封装返回数据
     * @param searchRequest
     * @return
     */
    private SearchResult searchResult(SearchRequest searchRequest,SearchParam param) {
        SearchResponse response = null;
        try {
            response = client.search(searchRequest, MallElasticSearchConfiguration.COMMON_OPTIONS);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 返回的实体
        SearchResult result = new SearchResult();
        // 检索后的数据
        SearchHits hits = response.getHits();

        // 1.查询到的所有的商品信息
        List<SkuESModel> skuESModelList = new ArrayList<>();
        SearchHit[] hitsList = hits.getHits();
        if (null != hitsList && hitsList.length > 0){
            for (SearchHit hit : hitsList) {
                String str = hit.getSourceAsString();
                SkuESModel skuESModel = JSON.parseObject(str, SkuESModel.class);
                if (!StringUtils.isNullOrEmpty(param.getKeyword())){
                    String subTitle = hit.getHighlightFields().get("subTitle").getFragments()[0].string();
                    skuESModel.setSubTitle(subTitle); // 设置标题高亮
                }
                skuESModelList.add(skuESModel);
            }
        }
        result.setProducts(skuESModelList);
        // 2.当前查询的所有的商品涉及到的所有的品牌信息
        List<SearchResult.BrandVO> brandsList = new ArrayList<>();
        Aggregations aggs = response.getAggregations(); // 获取聚合内容
        ParsedLongTerms brand_id_agg = aggs.get("brand_id_agg");
        List<? extends Terms.Bucket> buckets = brand_id_agg.getBuckets();
        if (null != buckets && buckets.size() > 0){
            for (Terms.Bucket bucket : buckets) {
                SearchResult.BrandVO brandVO = new SearchResult.BrandVO();
                String brandId = bucket.getKeyAsString(); // 品牌编号
                brandVO.setBrandId(Long.valueOf(brandId));
                // 品牌图片
                ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
                List<? extends Terms.Bucket> bucketsImg = brand_img_agg.getBuckets();
                if(null != bucketsImg && bucketsImg.size() > 0){
                    String imgKey = bucketsImg.get(0).getKeyAsString();
                    brandVO.setBrandImg(imgKey);
                }
                // 品牌名称
                ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
                List<? extends Terms.Bucket> brandNameBuckets = brand_name_agg.getBuckets();
                if (null != brandNameBuckets && brandNameBuckets.size()>0){
                    String nameKey = brandNameBuckets.get(0).getKeyAsString();
                    brandVO.setBrandName(nameKey);
                }
                brandsList.add(brandVO);
            }
        }
        result.setBrands(brandsList);
        // 3.当前查询的所有商品涉及到的所有的类别信息
        List<SearchResult.CatalogVO> catalogList = new ArrayList<>();
        ParsedLongTerms catalog_id_agg = aggs.get("catalog_id_agg");
        List<? extends Terms.Bucket> catalogIdBuckets = catalog_id_agg.getBuckets();
        if (null != catalogIdBuckets && catalogIdBuckets.size()>0){
            for (Terms.Bucket catalogIdBucket : catalogIdBuckets) {
                SearchResult.CatalogVO catalogVO = new SearchResult.CatalogVO();
                String catalogId = catalogIdBucket.getKeyAsString();
                catalogVO.setCatalogId(Long.valueOf(catalogId));// 类别编号
                ParsedStringTerms catalog_name_agg = catalogIdBucket.getAggregations().get("catalog_name_agg");
                List<? extends Terms.Bucket> catalogNameBuckets = catalog_name_agg.getBuckets();
                if (null != catalogNameBuckets && catalogNameBuckets.size() >0){
                    String catalogName = catalogNameBuckets.get(0).getKeyAsString();
                    catalogVO.setCatalogName(catalogName); // 类别名称
                }
                catalogList.add(catalogVO);
            }
        }
        result.setCatalogs(catalogList);
        // 4.当前查询的所有的商品涉及到的所有的属性信息
        List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
        ParsedNested attrs_agg = aggs.get("attrs_agg");
        ParsedLongTerms attr_id_agg = attrs_agg.getAggregations().get("attr_id_agg");
        List<? extends Terms.Bucket> attrIdBuckets = attr_id_agg.getBuckets();
        if (null != attrIdBuckets && attrIdBuckets.size()>0){
            for (Terms.Bucket attrIdBucket : attrIdBuckets) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                String attrId = attrIdBucket.getKeyAsString();
                attrVo.setAttrId(Long.valueOf(attrId));// 属性名称
                ParsedStringTerms attr_name_agg = attrIdBucket.getAggregations().get("attr_name_agg");
                List<? extends Terms.Bucket> attrNameBuckets = attr_name_agg.getBuckets();
                if (null != attrNameBuckets && attrNameBuckets.size()>0){
                    String attrName = attrNameBuckets.get(0).getKeyAsString();
                    attrVo.setAttrName(attrName);// 属性名称
                }
                ParsedStringTerms attr_value_agg = attrIdBucket.getAggregations().get("attr_value_agg");
                List<? extends Terms.Bucket> attrValueBuckets = attr_value_agg.getBuckets();
                if (null != attrValueBuckets && attrValueBuckets.size()>0){
                    List<String> valueList = attrValueBuckets.stream().map(item -> {
                        String attrValue = item.getKeyAsString();
                        return attrValue;
                    }).collect(Collectors.toList());
                    attrVo.setAttrValue(valueList);
                }
                attrVoList.add(attrVo);
            }
        }
        result.setAttrs(attrVoList);
        // 5.分页，当前页，总页数，总条数
        long totals = hits.getTotalHits().value;
        result.setTotal(totals); //总条数
        result.setPageNum(param.getPageNum()); // 当前页
        long totalPages = totals % ElasticSearchConstant.PRODUCT_PAGE_SIZE == 0 ? totals / ElasticSearchConstant.PRODUCT_PAGE_SIZE : totals / ElasticSearchConstant.PRODUCT_PAGE_SIZE+1;
        result.setTotalPages((int) totalPages); // 总页数
        ArrayList<Integer> pageList = new ArrayList<>();
        for (Integer i = 0; i < result.getTotalPages(); i++) {
            pageList.add(i+1);
        }
        result.setPageList(pageList);
        return result;
    }
}
